package com.qingzhuge.web.security;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qingzhuge.common.annotation.SecurityParameter;
import com.qingzhuge.controller.security.ControllerSecurityBody;
import com.qingzhuge.controller.security.RsaKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * @author : zero.xiao
 * @description :
 * @date :2019-08-12 16:56
 * @modified :
 */
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class MessageRequestBodyAdvice implements RequestBodyAdvice {
    @Resource
    private ControllerSecurityBody controllerSecurityBody;

    /**
     * 如果这里返回结果为false 就不会再走这个类了
     */
    @Override
    public boolean supports(@Nullable MethodParameter methodParameter, @Nullable Type type, @Nullable Class<? extends HttpMessageConverter<?>> aClass) {
        if (controllerSecurityBody.isEncrypt() && null != methodParameter) {
            SecurityParameter annotation = methodParameter.getMethodAnnotation(SecurityParameter.class);
            boolean decode = null != annotation && annotation.inDecode();
            if(decode) {
                log.debug("The request needs to be decoded : [{}]", methodParameter);
            }
            return decode;
        }
        return false;
    }

    @NotNull
    @Override
    public HttpInputMessage beforeBodyRead(@Nullable HttpInputMessage httpInputMessage,
                                           @Nullable MethodParameter methodParameter,
                                           @Nullable Type type,
                                           @Nullable Class<? extends HttpMessageConverter<?>> aClass) {
        if (null != methodParameter && null != httpInputMessage) {
            Method method = methodParameter.getMethod();
            String methodNmae = null;
            if (null != method){
                methodNmae = method.getName();
            }
            try {
                log.info("开始对接受值进行解密操作");
                // 定义是否解密
                //获取注解配置的包含和去除字段
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入参是否需要解密
                if (null != serializedField && serializedField.inDecode()) {
                    log.info("对方法method :[{}]数据进行解密!",methodNmae);
                    return new MyHttpInputMessage(httpInputMessage);
                }
            } catch (IOException e) {
                e.printStackTrace();
                log.error("对方法method :[{}]数据进行解密出现异常：[{}]",methodNmae,e.getMessage());
                throw new RuntimeException(String.format("对方法method :[%s]数据进行解密出现异常：[%s]",methodNmae,e.getMessage()));
            }
        }
        return httpInputMessage;
    }

    @Override
    public Object afterBodyRead(@Nullable Object body, @Nullable HttpInputMessage httpInputMessage, @Nullable MethodParameter methodParameter, @Nullable Type type, @Nullable Class<? extends HttpMessageConverter<?>> aClass) {
        log.debug("request : [{}]", body);
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, @Nullable HttpInputMessage httpInputMessage, @Nullable MethodParameter methodParameter, @Nullable Type type, @Nullable Class<? extends HttpMessageConverter<?>> aClass) {
        log.debug("request : [{}]", body);
        return body;
    }


    /**
     * @author : zero.xiao
     * @description :
     * @date :2019-08-12 20:52
     * @modified :这里实现了HttpInputMessage 封装一个自己的HttpInputMessage
     */
    class MyHttpInputMessage implements HttpInputMessage {
        HttpHeaders headers;
        InputStream body;

        MyHttpInputMessage(HttpInputMessage httpInputMessage) throws IOException {
            this.headers = httpInputMessage.getHeaders();
            // 解密操作
            this.body = decryptBody(httpInputMessage.getBody());
        }

        @NotNull
        @Override
        public InputStream getBody() {
            return body;
        }

        @NotNull
        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }

    /**
     * 对流进行解密操作
     */
    private InputStream decryptBody(InputStream in) {
        try {
            // 获取 json 字符串
            String bodyStr = IOUtils.toString(in, "UTF-8");
            if (StringUtils.isEmpty(bodyStr)) {
                throw new RuntimeException("无任何参数异常!");
            }
            // 获取 inputData 加密串
            ObjectMapper mapper = new ObjectMapper();
            JsonNode jsonNode = mapper.readTree(bodyStr);
            String inputData = jsonNode.path("data").asText();

            // 验证是否为空
            if (StringUtils.isEmpty(inputData)) {
                throw new RuntimeException("参数【data】缺失");
            } else {
                // 开始解密
                log.info("对加密串开始解密操作!");
                try {
                    byte[] dataBytes = decrypt(inputData);
                    if (null != dataBytes && dataBytes.length > 0) {
                        String decryptBody = new String(dataBytes);
                        log.info("操作结束!");
                        return IOUtils.toInputStream(decryptBody, "UTF-8");
                    }else{
                        return in;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("RSA解密异常：" + e.getMessage());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("获取参数【data】异常：" + e.getMessage());
        }
    }

    private byte[] decrypt(String inputData) {
        RsaKey rsaKey = controllerSecurityBody.get();
        byte[] bytes = new byte[]{};
        if (null != rsaKey){
            RSA rsa = SecureUtil.rsa(rsaKey.getPrivateKey(), rsaKey.getPublicKey());
            bytes = rsa.decrypt(inputData, KeyType.PrivateKey);
        }
        return bytes;
    }
}
